
package ch.hearc.meteo.imp.com.real.com;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import ch.hearc.meteo.imp.com.logique.MeteoServiceCallback_I;
import ch.hearc.meteo.imp.com.real.com.trame.TrameDecoder;
import ch.hearc.meteo.spec.meteo.exception.MeteoServiceException;
import ch.hearc.meteo.spec.meteo.listener.event.MeteoEventType_E;

public class ComConnexion implements ComConnexions_I
	{

	/*------------------------------------------------------------------*\
	|*							Constructeurs							*|
	\*------------------------------------------------------------------*/

	/**
	 * Ne pas oublier le setMeteoServiceCallback() !
	 */
	public ComConnexion(String portName, ComOption comOption)
		{
		this.comOption = comOption;
		this.portName = portName;
		}

	public ComConnexion(MeteoServiceCallback_I meteoServiceCallback, String portName, ComOption comOption)
		{
		this(portName, comOption);
		this.meteoServiceCallback = meteoServiceCallback;
		}

	/*------------------------------------------------------------------*\
	|*							Methodes Public							*|
	\*------------------------------------------------------------------*/

	@Override
	public void start() throws Exception
		{
		//rien, on est en �venementiel pour l'envoi / r�ception des donn�es
		}

	@Override
	public void stop() throws Exception
		{
		//rien, on est en �venementiel pour l'envoi / r�ception des donn�es
		}

	@Override
	public void connect() throws Exception
		{
		openPort();
		}

	@Override
	public void disconnect() throws Exception
		{
		closePort(CLOSE_TIMEOUT);
		}

	@Override
	public void askAltitudeAsync() throws Exception
		{
		byte[] requete = { CHAR_DEBUT_TRAME, CHAR_REQUETE_ALTITUDE, CHAR_FIN_TRAME };
		ecrireDonnees(requete);
		}

	@Override
	public void askPressionAsync() throws Exception
		{
		byte[] requete = { CHAR_DEBUT_TRAME, CHAR_REQUETE_PRESSION, CHAR_FIN_TRAME };
		ecrireDonnees(requete);
		}

	@Override
	public void askTemperatureAsync() throws Exception
		{
		byte[] requete = { CHAR_DEBUT_TRAME, CHAR_REQUETE_TEMPERATURE, CHAR_FIN_TRAME };
		ecrireDonnees(requete);
		}

	/*------------------------------*\
	|*				Get				*|
	\*------------------------------*/

	@Override
	public String getNamePort()
		{
		return portName;
		}

	/*------------------------------*\
	|*				Set				*|
	\*------------------------------*/

	public void setMeteoServiceCallback(MeteoServiceCallback_I meteoServiceCallback)
		{
		this.meteoServiceCallback = meteoServiceCallback;
		}

	/*------------------------------------------------------------------*\
	|*							Methodes Private						*|
	\*------------------------------------------------------------------*/

	private synchronized byte[] lireDonnees()
		{
		byte[] buffer = new byte[1024];
		byte[] result = new byte[10];

		int i = 0;
		byte c = 0;
		byte lastC = 0;

		try
			{
			while((c = (byte)is.read()) != -1)
				{
				buffer[i] = c;
				if (lastC == '\r' && c == '\n')
					{
					for(int j = 0; j < 10; j++)
						{
						result[9 - j] = buffer[i - j];
						}
					return result;
					}
				lastC = c;
				i++;
				}
			}
		catch (IOException e)
			{
			e.printStackTrace();
			}

		return null;
		}

	private void ecrireDonnees(byte[] data)
		{
		try
			{
			os.write(data);
			}
		catch (IOException e)
			{
			e.printStackTrace();
			}
		}

	private void traiterDonneesEntrantes()
		{
		try
			{
			byte[] trame = lireDonnees();

			MeteoEventType_E meteoEventType = TrameDecoder.dataType(trame);
			float valeur = TrameDecoder.valeur(trame);

			switch(meteoEventType)
				{
				case ALTITUDE:
					meteoServiceCallback.altitudePerformed(valeur);
					break;
				case PRESSION:
					meteoServiceCallback.pressionPerformed(valeur);
					break;
				case TEMPERATURE:
					meteoServiceCallback.temperaturePerformed(valeur);
					break;
				}
			}
		catch (MeteoServiceException e)
			{
			e.printStackTrace();
			}
		}

	/**
	 * @return true si le port est ouvert correctement
	 */
	private boolean openPort()
		{
		try
			{
			CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(portName);
			int openDelay = 5000;
			serialPort = (SerialPort)portId.open(ComConnexion.class.getSimpleName(), openDelay);
			comOption.applyTo(serialPort);
			serialPort.notifyOnDataAvailable(true);
			serialPort.addEventListener(new SerialPortEventListener()
				{

					@Override
					public void serialEvent(SerialPortEvent event)
						{
						switch(event.getEventType())
							{
							case SerialPortEvent.DATA_AVAILABLE:
								traiterDonneesEntrantes();
								break;
							}
						}
				});

			is = serialPort.getInputStream();
			os = serialPort.getOutputStream();
			}
		catch (Exception e)
			{
			return false;
			}
		return true;
		}

	private synchronized void closePort(long timeout)
		{
		ExecutorService executor = null;
		try
			{
			Callable<Void> readTask = new Callable<Void>()
				{

					@Override
					public Void call() throws Exception
						{
						try
							{
							serialPort.removeEventListener();
							is.close();
							os.close();
							serialPort.close();
							}
						catch (Exception e)
							{
							e.printStackTrace();
							}
						return null;
						}
				};
			executor = Executors.newFixedThreadPool(1);
			executor.awaitTermination(timeout + 50, TimeUnit.MILLISECONDS);
			Future<Void> future = executor.submit(readTask);
			Void result = future.get(timeout, TimeUnit.MILLISECONDS);
			executor.shutdown();
			}
		catch (Exception e)
			{
			executor.shutdown();
			}
		}

	/*------------------------------------------------------------------*\
	|*							Attributs Private						*|
	\*------------------------------------------------------------------*/

	// Input
	private ComOption comOption;
	private String portName;
	private MeteoServiceCallback_I meteoServiceCallback;

	// Tools
	private SerialPort serialPort;
	private BufferedWriter writer;
	private BufferedReader reader;
	private InputStream is;
	private OutputStream os;

	/*------------------------------*\
	|*			  Static			*|
	\*------------------------------*/

	private static final byte CHAR_DEBUT_TRAME = 0x01;
	private static final byte CHAR_FIN_TRAME = 0x00;
	private static final byte CHAR_REQUETE_PRESSION = 0x00;
	private static final byte CHAR_REQUETE_TEMPERATURE = 0x01;
	private static final byte CHAR_REQUETE_ALTITUDE = 0x02;

	private static final long CLOSE_TIMEOUT = 350;

	}
